diff --git a/django/middleware/cache.py b/django/middleware/cache.py
index 34bf0ca..049238e 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
@@ -48,12 +48,72 @@ More details about how the caching works:
 
 """
 
+import hashlib
+
+from django.utils.encoding import iri_to_uri
 from django.conf import settings
 from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
-from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age
+from django.utils.cache import patch_response_headers, get_max_age, cc_delim_re
+from django.utils.timezone import get_current_timezone_name
+from django.utils.translation import get_language
 
+class TwoPartCacheMiddlewareBase(object):
+    @classmethod
+    def get_cache_key(cls, request, key_prefix=None, method='GET', cache=None):
+        """
+        Returns a cache key based on the request path and query. It can be used
+        in the request phase because it pulls the list of headers to take into
+        account from the global path registry and uses those to build a cache key
+        to check against.
 
-class UpdateCacheMiddleware(object):
+        If there is no headerlist stored, the page needs to be rebuilt, so this
+        function returns None.
+        """
+        if key_prefix is None:
+            key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+        cache_key = cls._generate_cache_header_key(key_prefix, request)
+        if cache is None:
+            cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
+        headerlist = cache.get(cache_key, None)
+        if headerlist is not None:
+            return cls._generate_cache_key(request, method, headerlist, key_prefix)
+        else:
+            return None
+
+    @classmethod
+    def _i18n_cache_key_suffix(cls, request, cache_key):
+        """If necessary, adds the current locale or time zone to the cache key."""
+        if settings.USE_I18N or settings.USE_L10N:
+            # first check if LocaleMiddleware or another middleware added
+            # LANGUAGE_CODE to request, then fall back to the active language
+            # which in turn can also fall back to settings.LANGUAGE_CODE
+            cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
+        if settings.USE_TZ:
+            cache_key += '.%s' % get_current_timezone_name()
+        return cache_key
+
+    @classmethod
+    def _generate_cache_key(cls, request, method, headerlist, key_prefix):
+        """Returns a cache key from the headers given in the header list."""
+        ctx = hashlib.md5()
+        for header in headerlist:
+            value = request.META.get(header, None)
+            if value is not None:
+                ctx.update(value)
+        path = hashlib.md5(iri_to_uri(request.get_full_path()))
+        cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
+            key_prefix, method, path.hexdigest(), ctx.hexdigest())
+        return cls._i18n_cache_key_suffix(request, cache_key)
+
+    @classmethod
+    def _generate_cache_header_key(cls, key_prefix, request):
+        """Returns a cache key for the header cache."""
+        path = hashlib.md5(iri_to_uri(request.get_full_path()))
+        cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
+            key_prefix, path.hexdigest())
+        return cls._i18n_cache_key_suffix(request, cache_key)
+
+class UpdateCacheMiddleware(TwoPartCacheMiddlewareBase):
     """
     Response-phase cache middleware that updates the cache if the response is
     cacheable.
@@ -88,6 +148,38 @@ class UpdateCacheMiddleware(object):
                 return False
         return True
 
+    @classmethod
+    def learn_cache_key(cls, request, response, cache_timeout=None, key_prefix=None, cache=None):
+        """
+        Learns what headers to take into account for some request path from the
+        response object. It stores those headers in a global path registry so that
+        later access to that path will know what headers to take into account
+        without building the response object itself. The headers are named in the
+        Vary header of the response, but we want to prevent response generation.
+
+        The list of headers to use for cache key generation is stored in the same
+        cache as the pages themselves. If the cache ages some data out of the
+        cache, this just means that we have to build the response once to get at
+        the Vary header and so at the list of headers to use for the cache key.
+        """
+        if key_prefix is None:
+            key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+        if cache_timeout is None:
+            cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
+        cache_key = cls._generate_cache_header_key(key_prefix, request)
+        if cache is None:
+            cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
+        if response.has_header('Vary'):
+            headerlist = ['HTTP_'+header.upper().replace('-', '_')
+                          for header in cc_delim_re.split(response['Vary'])]
+            cache.set(cache_key, headerlist, cache_timeout)
+            return cls._generate_cache_key(request, request.method, headerlist, key_prefix)
+        else:
+            # if there is no Vary header, we still need a cache key
+            # for the request.get_full_path()
+            cache.set(cache_key, [], cache_timeout)
+            return cls._generate_cache_key(request, request.method, [], key_prefix)
+
     def process_response(self, request, response):
         """Sets the cache, if needed."""
         if not self._should_update_cache(request, response):
@@ -106,7 +198,7 @@ class UpdateCacheMiddleware(object):
             return response
         patch_response_headers(response, timeout)
         if timeout:
-            cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
+            cache_key = self.learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
             if hasattr(response, 'render') and callable(response.render):
                 response.add_post_render_callback(
                     lambda r: self.cache.set(cache_key, r, timeout)
@@ -115,7 +207,7 @@ class UpdateCacheMiddleware(object):
                 self.cache.set(cache_key, response, timeout)
         return response
 
-class FetchFromCacheMiddleware(object):
+class FetchFromCacheMiddleware(TwoPartCacheMiddlewareBase):
     """
     Request-phase cache middleware that fetches a page from the cache.
 
@@ -140,14 +232,14 @@ class FetchFromCacheMiddleware(object):
             return None # Don't bother checking the cache.
 
         # try and get the cached GET response
-        cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
+        cache_key = self.get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
         if cache_key is None:
             request._cache_update_cache = True
             return None # No cache information available, need to rebuild.
         response = self.cache.get(cache_key, None)
         # if it wasn't found and we are looking for a HEAD, try looking just for that
         if response is None and request.method == 'HEAD':
-            cache_key = get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
+            cache_key = self.get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
             response = self.cache.get(cache_key, None)
 
         if response is None:
diff --git a/django/utils/cache.py b/django/utils/cache.py
index 1015c2f..9448aae 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -23,10 +23,8 @@ import time
 
 from django.conf import settings
 from django.core.cache import get_cache
-from django.utils.encoding import smart_str, iri_to_uri
+from django.utils.encoding import smart_str
 from django.utils.http import http_date
-from django.utils.timezone import get_current_timezone_name
-from django.utils.translation import get_language
 
 cc_delim_re = re.compile(r'\s*,\s*')
 
@@ -122,12 +120,6 @@ def patch_response_headers(response, cache_timeout=None):
         response['Expires'] = http_date(time.time() + cache_timeout)
     patch_cache_control(response, max_age=cache_timeout)
 
-def add_never_cache_headers(response):
-    """
-    Adds headers to a response to indicate that a page should never be cached.
-    """
-    patch_response_headers(response, cache_timeout=-1)
-
 def patch_vary_headers(response, newheaders):
     """
     Adds (or updates) the "Vary" header in the given HttpResponse object.
@@ -157,88 +149,6 @@ def has_vary_header(response, header_query):
     existing_headers = set([header.lower() for header in vary_headers])
     return header_query.lower() in existing_headers
 
-def _i18n_cache_key_suffix(request, cache_key):
-    """If necessary, adds the current locale or time zone to the cache key."""
-    if settings.USE_I18N or settings.USE_L10N:
-        # first check if LocaleMiddleware or another middleware added
-        # LANGUAGE_CODE to request, then fall back to the active language
-        # which in turn can also fall back to settings.LANGUAGE_CODE
-        cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
-    if settings.USE_TZ:
-        cache_key += '.%s' % get_current_timezone_name()
-    return cache_key
-
-def _generate_cache_key(request, method, headerlist, key_prefix):
-    """Returns a cache key from the headers given in the header list."""
-    ctx = hashlib.md5()
-    for header in headerlist:
-        value = request.META.get(header, None)
-        if value is not None:
-            ctx.update(value)
-    path = hashlib.md5(iri_to_uri(request.get_full_path()))
-    cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
-        key_prefix, method, path.hexdigest(), ctx.hexdigest())
-    return _i18n_cache_key_suffix(request, cache_key)
-
-def _generate_cache_header_key(key_prefix, request):
-    """Returns a cache key for the header cache."""
-    path = hashlib.md5(iri_to_uri(request.get_full_path()))
-    cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
-        key_prefix, path.hexdigest())
-    return _i18n_cache_key_suffix(request, cache_key)
-
-def get_cache_key(request, key_prefix=None, method='GET', cache=None):
-    """
-    Returns a cache key based on the request path and query. It can be used
-    in the request phase because it pulls the list of headers to take into
-    account from the global path registry and uses those to build a cache key
-    to check against.
-
-    If there is no headerlist stored, the page needs to be rebuilt, so this
-    function returns None.
-    """
-    if key_prefix is None:
-        key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
-    cache_key = _generate_cache_header_key(key_prefix, request)
-    if cache is None:
-        cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
-    headerlist = cache.get(cache_key, None)
-    if headerlist is not None:
-        return _generate_cache_key(request, method, headerlist, key_prefix)
-    else:
-        return None
-
-def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=None):
-    """
-    Learns what headers to take into account for some request path from the
-    response object. It stores those headers in a global path registry so that
-    later access to that path will know what headers to take into account
-    without building the response object itself. The headers are named in the
-    Vary header of the response, but we want to prevent response generation.
-
-    The list of headers to use for cache key generation is stored in the same
-    cache as the pages themselves. If the cache ages some data out of the
-    cache, this just means that we have to build the response once to get at
-    the Vary header and so at the list of headers to use for the cache key.
-    """
-    if key_prefix is None:
-        key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
-    if cache_timeout is None:
-        cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
-    cache_key = _generate_cache_header_key(key_prefix, request)
-    if cache is None:
-        cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
-    if response.has_header('Vary'):
-        headerlist = ['HTTP_'+header.upper().replace('-', '_')
-                      for header in cc_delim_re.split(response['Vary'])]
-        cache.set(cache_key, headerlist, cache_timeout)
-        return _generate_cache_key(request, request.method, headerlist, key_prefix)
-    else:
-        # if there is no Vary header, we still need a cache key
-        # for the request.get_full_path()
-        cache.set(cache_key, [], cache_timeout)
-        return _generate_cache_key(request, request.method, [], key_prefix)
-
 
 def _to_tuple(s):
     t = s.split('=',1)
diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py
index a39cc54..2847e05 100644
--- a/django/views/decorators/cache.py
+++ b/django/views/decorators/cache.py
@@ -1,6 +1,6 @@
 from functools import wraps
 from django.utils.decorators import decorator_from_middleware_with_args, available_attrs
-from django.utils.cache import patch_cache_control, add_never_cache_headers
+from django.utils.cache import patch_cache_control, patch_response_headers 
 from django.middleware.cache import CacheMiddleware
 
 
@@ -86,6 +86,6 @@ def never_cache(view_func):
     @wraps(view_func, assigned=available_attrs(view_func))
     def _wrapped_view_func(request, *args, **kwargs):
         response = view_func(request, *args, **kwargs)
-        add_never_cache_headers(response)
+        patch_response_headers(response, cache_timeout=0)
         return response
     return _wrapped_view_func
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index 307588c..ea13421 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -19,15 +19,15 @@ from django.core.cache.backends.base import (CacheKeyWarning,
 from django.db import router
 from django.http import HttpResponse, HttpRequest, QueryDict
 from django.middleware.cache import (FetchFromCacheMiddleware,
-    UpdateCacheMiddleware, CacheMiddleware)
+    UpdateCacheMiddleware, CacheMiddleware, TwoPartCacheMiddlewareBase)
 from django.template import Template
 from django.template.response import TemplateResponse
 from django.test import TestCase, TransactionTestCase, RequestFactory
 from django.test.utils import (get_warnings_state, restore_warnings_state,
     override_settings)
 from django.utils import timezone, translation, unittest
-from django.utils.cache import (patch_vary_headers, get_cache_key,
-    learn_cache_key, patch_cache_control, patch_response_headers)
+from django.utils.cache import (patch_vary_headers, 
+    patch_cache_control, patch_response_headers)
 from django.views.decorators.cache import cache_page
 
 from .models import Poll, expensive_calculation
@@ -998,22 +998,11 @@ class CacheUtils(TestCase):
     """TestCase for django.utils.cache functions."""
 
     def setUp(self):
-        self.path = '/cache/test/'
         self.cache = get_cache('default')
 
     def tearDown(self):
         self.cache.clear()
 
-    def _get_request(self, path, method='GET'):
-        request = HttpRequest()
-        request.META = {
-            'SERVER_NAME': 'testserver',
-            'SERVER_PORT': 80,
-        }
-        request.method = method
-        request.path = request.path_info = "/cache/%s" % path
-        return request
-
     def test_patch_vary_headers(self):
         headers = (
             # Initial vary, new headers, resulting vary.
@@ -1034,37 +1023,6 @@ class CacheUtils(TestCase):
             patch_vary_headers(response, newheaders)
             self.assertEqual(response['Vary'], resulting_vary)
 
-    def test_get_cache_key(self):
-        request = self._get_request(self.path)
-        response = HttpResponse()
-        key_prefix = 'localprefix'
-        # Expect None if no headers have been set yet.
-        self.assertEqual(get_cache_key(request), None)
-        # Set headers to an empty list.
-        learn_cache_key(request, response)
-        self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
-        # Verify that a specified key_prefix is taken into account.
-        learn_cache_key(request, response, key_prefix=key_prefix)
-        self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
-
-    def test_get_cache_key_with_query(self):
-        request = self._get_request(self.path + '?test=1')
-        response = HttpResponse()
-        # Expect None if no headers have been set yet.
-        self.assertEqual(get_cache_key(request), None)
-        # Set headers to an empty list.
-        learn_cache_key(request, response)
-        # Verify that the querystring is taken into account.
-        self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
-
-    def test_learn_cache_key(self):
-        request = self._get_request(self.path, 'HEAD')
-        response = HttpResponse()
-        response['Vary'] = 'Pony'
-        # Make sure that the Vary header is added to the key hash
-        learn_cache_key(request, response)
-        self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
-
     def test_patch_cache_control(self):
         tests = (
             # Initial Cache-Control, kwargs to patch_cache_control, expected Cache-Control parts
@@ -1342,6 +1300,7 @@ class CacheMiddlewareTest(TestCase):
 
     def setUp(self):
         self.factory = RequestFactory()
+        self.path = '/cache/test/'
         self.default_cache = get_cache('default')
         self.other_cache = get_cache('other')
 
@@ -1381,6 +1340,48 @@ class CacheMiddlewareTest(TestCase):
         self.assertEqual(as_view_decorator_with_custom.cache_alias, 'other')
         self.assertEqual(as_view_decorator_with_custom.cache_anonymous_only, True)
 
+    def _get_request(self, path, method='GET'):
+        request = HttpRequest()
+        request.META = {
+            'SERVER_NAME': 'testserver',
+            'SERVER_PORT': 80,
+        }
+        request.method = method
+        request.path = request.path_info = "/cache/%s" % path
+        return request
+
+    def test_get_cache_key(self):
+        request = self._get_request(self.path)
+        response = HttpResponse()
+        key_prefix = 'localprefix'
+        # Expect None if no headers have been set yet.
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
+        # Set headers to an empty list.
+        UpdateCacheMiddleware.learn_cache_key(request, response)
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+        # Verify that a specified key_prefix is taken into account.
+        UpdateCacheMiddleware.learn_cache_key(request, response, key_prefix=key_prefix)
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+
+    def test_get_cache_key_with_query(self):
+        request = self._get_request(self.path + '?test=1')
+        response = HttpResponse()
+        # Expect None if no headers have been set yet.
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
+        # Set headers to an empty list.
+        UpdateCacheMiddleware.learn_cache_key(request, response)
+        # Verify that the querystring is taken into account.
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
+
+    def test_learn_cache_key(self):
+        request = self._get_request(self.path, 'HEAD')
+        response = HttpResponse()
+        response['Vary'] = 'Pony'
+        # Make sure that the Vary header is added to the key hash
+        UpdateCacheMiddleware.learn_cache_key(request, response)
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+
+
     def test_middleware(self):
         middleware = CacheMiddleware()
         prefix_middleware = CacheMiddleware(key_prefix='prefix1')
@@ -1624,23 +1625,23 @@ class TestWithTemplateResponse(TestCase):
         response = TemplateResponse(HttpResponse(), Template("This is a test"))
         key_prefix = 'localprefix'
         # Expect None if no headers have been set yet.
-        self.assertEqual(get_cache_key(request), None)
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
         # Set headers to an empty list.
-        learn_cache_key(request, response)
-        self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+        UpdateCacheMiddleware.learn_cache_key(request, response)
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
         # Verify that a specified key_prefix is taken into account.
-        learn_cache_key(request, response, key_prefix=key_prefix)
-        self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+        UpdateCacheMiddleware.learn_cache_key(request, response, key_prefix=key_prefix)
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
 
     def test_get_cache_key_with_query(self):
         request = self._get_request(self.path + '?test=1')
         response = TemplateResponse(HttpResponse(), Template("This is a test"))
         # Expect None if no headers have been set yet.
-        self.assertEqual(get_cache_key(request), None)
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
         # Set headers to an empty list.
-        learn_cache_key(request, response)
+        UpdateCacheMiddleware.learn_cache_key(request, response)
         # Verify that the querystring is taken into account.
-        self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
+        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
 
     @override_settings(USE_ETAGS=False)
     def test_without_etag(self):
